package com.crois.barrier.web.handler;

import com.crois.barrier.common.constants.Constants;
import com.crois.barrier.common.dto.ApiRouteInfo;
import com.crois.barrier.common.exception.BarrierException;
import com.crois.barrier.common.exception.BarrierExceptionEnum;
import com.crois.barrier.plugin.BarrierPlugin;
import com.crois.barrier.plugin.BarrierPluginChain;
import com.crois.barrier.web.cache.CacheManager;
import com.crois.barrier.web.support.ServerWebExchangeUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import java.util.List;
import java.util.Objects;

/**
 * @author Hou Ze Yu
 * @description barrier web请求处理器
 * @date 2020/7/12
 */
public final class BarrierWebHandler extends DispatcherHandler {

    Logger LOGGER = LoggerFactory.getLogger(BarrierWebHandler.class);

    private List<BarrierPlugin> pluginList;

    private Scheduler scheduler;

    private CacheManager cacheManager;

    @Override
    public Mono<Void> handle(final ServerWebExchange serverWebExchange) {

        String path = serverWebExchange.getRequest().getPath().toString();

        serverWebExchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_REQUEST_PATH,path);
        /**
         * 匹配到可以route
         */
        Boolean isMatch =  UrlHandler.judgeIsMatch(cacheManager,path,serverWebExchange);
        /**
         * 判断当前route的环境是否匹配
         */
        if (isMatch){
            // 从header 中 获取 stage
            HttpHeaders headers = serverWebExchange.getRequest().getHeaders();
            List<String> headerStageTagList = headers.get(Constants.HEADER_STAGE_TAG);
            if (CollectionUtils.isNotEmpty(headerStageTagList)){
                String headerStageTag = headerStageTagList.get(0);
                if (StringUtils.isNotEmpty(headerStageTag)) {
                    ApiRouteInfo checkedApiRouteInfo = (ApiRouteInfo) serverWebExchange.getAttributes().get(ServerWebExchangeUtils.BARRIER_ROUTE_ATTR);
                    if (headerStageTag.equals(Constants.TEST_ENV)){
                        if (Boolean.FALSE.equals(checkedApiRouteInfo.getEnvTestPublish())) {
                            throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_NOT_FIND_AVAILABLE_URL);
                        }
                    }else if (headerStageTag.equals(Constants.PROD_ENV)){
                        if (Boolean.FALSE.equals(checkedApiRouteInfo.getEnvProdPublish())) {
                            throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_NOT_FIND_AVAILABLE_URL);
                        }
                    }else if (headerStageTag.equals(Constants.BETA_ENV)){
                        if (Boolean.FALSE.equals(checkedApiRouteInfo.getEnvBetaPublish())) {
                            throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_NOT_FIND_AVAILABLE_URL);
                        }
                    }else {
                        throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_ENV_FAIL);
                    }
                }
            }else {
                throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_NOT_ENV_FAIL);
            }
            LOGGER.info("[match api route success],path:{} invoke plugin ",path);

            return new DefaultDoorPluginChain(pluginList).execute(serverWebExchange).subscribeOn(scheduler);

        }else {
            LOGGER.error("[ not match api route ],path:{}  ",path);
            throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_ROUTE_NOT_MATCH);
        }
    }

    public BarrierWebHandler(final List<BarrierPlugin> pluginList, CacheManager cacheManager) {
        this.pluginList = pluginList;
        this.cacheManager = cacheManager;
        String schedulerType = System.getProperty("barrier.scheduler.type", "fixed");

        if (Objects.equals(schedulerType,"fixed")){
             int threads = Integer.parseInt(
                     System.getProperty("barrier.scheduler.type",
                             ""+
                             Math.max((Runtime.getRuntime()
                                     .availableProcessors()<<1)+1,16)));
             scheduler = Schedulers.newParallel("barrier-work-threads",threads);
        }else {
            scheduler = Schedulers.elastic();
        }

    }


    private static class DefaultDoorPluginChain implements BarrierPluginChain {

        private int index;

        private final List<BarrierPlugin> plugins;

        /**
         * Instantiates a new Default soul plugin chain.
         *
         * @param plugins the plugins
         */
        DefaultDoorPluginChain(final List<BarrierPlugin> plugins) {
            this.plugins = plugins;
        }

        /**
         * Delegate to the next {@code WebFilter} in the chain.
         *
         * @param exchange the current server exchange
         * @return {@code Mono<Void>} to indicate when request handling is complete
         */
        @Override
        public Mono<Void> execute(final ServerWebExchange exchange) {

            if (CollectionUtils.isEmpty(plugins)){
                return Mono.empty();
            }
            return Mono.defer(() -> {
                if (this.index < plugins.size()) {
                    BarrierPlugin plugin = plugins.get(this.index++);
                    Boolean skip = plugin.skip(exchange);
                    if (skip) {
                        return this.execute(exchange);
                    } else {
                        return plugin.execute(exchange, this);
                    }
                } else {
                    return Mono.empty();
                }
            });
        }
    }
}
